﻿namespace DotNetCommon
{
    using System;
    using System.Collections.Concurrent;

    /// <summary>
    /// 一个通用的线程安全的对象池定义
    /// </summary>
    /// <example>
    /// <para>示例代码: </para>
    /// <code>
    /// public class Program
    /// {
    ///     public static void Main(string[] args)
    ///     {
    ///         //配置并创建对象池
    ///         var pool = new EasyPool&lt;Person>(
    ///             () => new Person(),//Person实例创建方法
    ///             person => person.Init(),//对象池回收Person执行的逻辑
    ///             10//池中最大的数量
    ///         );
    ///         
    ///         //从对象池中取一个person实例
    ///         var person=pool.Rent();
    ///         //do something
    ///         //将person实例归还到对象池中
    ///         pool.Return(person);
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <remarks>
    /// 微软的扩展包里已提供了这项功能，但仅支持netstandard2.0：
    /// <code><PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.1.8" /></code>
    /// </remarks>
    /// <typeparam name="T">对象池中存放的对象</typeparam>
    public interface IEasyPool<T> : IDisposable where T : class
    {
        /// <summary>
        /// 当前池中的模型数量
        /// </summary>
        int Count { get; }

        /// <summary>
        /// 从对象池中获取模型
        /// </summary>
        T Get();

        /// <summary>
        /// 将模型归还到对象池中(可指定是否需要执行预设的重置逻辑)
        /// </summary>
        /// <param name="item">要规划的模型</param>
        /// <param name="reset">
        /// 是否需要执行预设的重置逻辑
        /// </param>
        /// <returns>归还成功返回True，负责返回False(比如：池中的模型数量已达最大值)</returns>
        bool Return(T item, bool reset = true);
    }


    /// <summary>
    /// 一个通用的线程安全的对象池定义
    /// </summary>
    /// <example>
    /// <para>示例代码: </para>
    /// <code>
    /// public class Program
    /// {
    ///     public static void Main(string[] args)
    ///     {
    ///         //配置并创建对象池
    ///         var pool = new EasyPool&lt;Person>(
    ///             () => new Person(),//Person实例创建方法
    ///             person => person.Init(),//对象池回收Person执行的逻辑
    ///             10//池中最大的数量
    ///         );
    ///         
    ///         //从对象池中取一个person实例
    ///         var person=pool.Rent();
    ///         //do something
    ///         //将person实例归还到对象池中
    ///         pool.Return(person);
    ///     }
    /// }
    /// </code>
    /// </example>
    /// <remarks>
    /// 微软的扩展包里已提供了这项功能，但仅支持netstandard2.0：
    /// <code><PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.1.8" /></code>
    /// </remarks>
    /// <typeparam name="T">对象池中存放的对象</typeparam>
    public sealed class EasyPool<T> : IEasyPool<T> where T : class
    {
        private readonly ConcurrentBag<T> _pool;
        private readonly Func<T> _factory;
        private readonly Action<T> _reset;
        private readonly int _maxCount;

        /// <summary>
        /// 创建一个<see cref="EasyPool{T}"/>实例
        /// </summary>
        /// <param name="factory">创建<typeparamref name="T"/>实例的工厂方法</param>
        /// <param name="reset">将对象归还到池子前对对象执行的清理逻辑</param>
        /// <param name="maxCount">最多缓存的数量</param>
        public EasyPool(Func<T> factory, Action<T> reset, int maxCount)
        {
            _factory = Ensure.NotNull(factory, nameof(factory));
            _reset = reset;
            _maxCount = maxCount;

            _pool = new ConcurrentBag<T>();
        }

        /// <summary>
        /// 获取池子中对象的数量
        /// </summary>
        public int Count => (int)_pool.Count;

        /// <summary>
        /// 从对象池中获取一个实例
        /// </summary>
        public T Get() => _pool.TryTake(out T item) ? item : _factory();

        /// <summary>
        /// 将实例归还到池子中,对象成功入池返回True,否则返回False(对象池中的实例数量已达最大值)
        /// </summary>
        /// <param name="item">要归还的实例</param>
        /// <param name="reset">
        /// 这个实例是否需要执行清理逻辑
        /// </param>
        /// <returns></returns>
        public bool Return(T item, bool reset = true)
        {
            if (reset) { _reset?.Invoke(item); }
            if (_pool.Count >= _maxCount)
            {
                //未成功归还，尝试触发Dispose方法
                if (item is IDisposable t) try { t.Dispose(); } catch { };
                return false;
            }

            _pool.Add(item);
            return true;
        }

        /// <summary>
        /// 释放池子中的实例
        /// </summary>
        public void Dispose()
        {
            while (_pool.TryTake(out T inst))
            {
                if (inst is IDisposable t) try { t.Dispose(); } catch { };
            }
        }
    }
}